<?php
	
	namespace Aspic\Database;
	
	abstract class AbstractQueryBuilder {
		
		protected $_sqlPartsData;
		
		protected $_sqlUtil;
		protected $_queryExecuter;
		protected $_rowsClass;
		
		/**
		* @param bool $QueryExecuterInterface If set, user can execute directly the generated request with "getResults" method
		*/
		public function __construct(Util $sqlUtilObj, QueryExecuterInterface $QueryExecuterInterface = null) {
			$this->_sqlParts = array();
			$this->_sqlUtil = $sqlUtilObj;
			$this->_queryExecuter = $QueryExecuterInterface;
			$this->_rowsClass = 'stdclass';
		}
		
		/**
		* Define a class that will replace stdclass (each line return by the db will make an object of this class) when using getResults()
		*/
		public function setRowClass($className) {
			if(!class_exists($className)) {
				throw new A\Exception('Class "'.$className.'" does not exists');
			}
			else {
				$this->_rowsClass = $className;
			}
		}
		
		/**
		* Select the main table and all columns to retrieve
		* 
		* @param $table Table name and it's alias (optionnal) separate with a space like: "my_table" OR "my_table mtAlias"
		* @param  array|string $cols The columns to select from this table. (array: array('table.column' => 'alias'))
		*/
		public function from($table, $cols = '*', $distinct = false) {
			$this->_registerSqlData('from',
				array(
					'table' => $table, 
					'cols' => $cols, 
					'distinct' => $distinct, 
				)
			);
			
			return $this;
		}
		
		/**
		* This set only the columns we need (and not the table like from())
		*/
		public function columns($cols) {
			if (is_null($cols)) {
				$cols = '*';
			}
			
			// from clause must be set before
			if($from = $this->_getSqlData('from')) {
				$from['cols'] = $cols;
				
				$this->_registerSqlData('from', $from);
			}
			
			return $this;
		}
		
		/**
		* Add columns to retrieve
		*/
		public function addColumns($cols) {
			// from clause must be set before
			if($from = $this->_getSqlData('from')) {
				if(is_array($from['cols'] AND is_array($cols))) {
					$from['cols'] = array_merge($from['cols'], $cols);
				}
				elseif($from['cols'] != '' AND $from['cols'] != '*') {
					$from['cols'] .= ', '.$cols;
				}
				else {
					$from['cols'] = $cols;
				}
				
				$this->_registerSqlData('from', $from);
			}
			
			return $this;
		}
		
		protected function _getFromSql() {
			if($data = $this->_getSqlData('from')) {
				
				$table = $data['table'];
				$cols = $data['cols'];
			
				if(is_string($cols)) {
					$colsStr = $cols;
				}
				else {
					$arrColsStr = array();
					
					foreach($cols as $colName => $colAlias) {
						if (is_numeric($colName)) {
							$arrColsStr[] = $colAlias;
						}
						else {
							$arrColsStr[] = $colName.' AS "'.$colAlias.'"';
						}
					}
					
					$colsStr = implode(', ', $arrColsStr);
				}
				
				$distinct = '';
				
				if($data['distinct']) {
					$distinct = ' DISTINCT';
				}
				
				return 'SELECT'.$distinct.' '.$colsStr.' FROM '.$this->_getTableSqlString($table);
			}
			else {
				return '';
			}
		}
		
		public function join($table, $onData) {
			$this->_join($table,$onData);
			
			return $this;
		}
		
		public function joinLeft($table, $onData) {
			$this->_join($table,$onData, 'LEFT OUTER');
			
			return $this;
		}
		
		public function joinRight($table, $onData) {
			$this->_join($table,$onData, 'RIGHT OUTER');
			
			return $this;
		}
		
		public function joinFull($table, $onData) {
			$this->_join($table,$onData, 'FULL OUTER');
			
			return $this;
		}
		
		protected function _join($table, $onData = '', $prefix = '') {
			$this->_appendSqlData('join', array(
				'table' => $table, 'onData' => $onData, 'prefix' => $prefix)
			);
		}
		
		protected function _getJoinSql() {
			if($data = $this->_getSqlData('join')) {
				$sqlParts = array();
				
				foreach($data as $join) {
					$table = $join['table'];
					$onData = $join['onData'];
					$prefix = $join['prefix'];
					
					$onSql = $this->_sqlUtil->getJoinOnStr($onData);
					
					if($onSql) {
						$onSql = ' '.$onSql;
					}
				
					$sqlParts[] = (($prefix)?$prefix.' ':'').'JOIN '.$this->_getTableSqlString($table).$onSql;
					
				}
				
				$sql = implode(" \n", $sqlParts);
				
				return $sql;
			}
			else {
				return '';
			}
		}	
		
		/**
		* Add a where clause to the query
		* This function could be called more than once, and the differents parts
		* will be joined with the "AND" operator
		*/
		public function where($where, array $params = array()) { // , param1, param2
			$lastOrIndex = max(0,count($this->_getSqlData('where')) - 1);
			
			if(is_string($where) AND func_num_args() > 1) { // quote into
				$params = array_slice(func_get_args(), 1);
				
				$where = $this->_sqlUtil->quoteIntoArray($where, $params);
			}
			else {
				$where = $this->_sqlUtil->getWhereString($where, false, $params);
			}
			
			if($where) {
				$this->_sqlPartsData['where'][$lastOrIndex][] = $where;
			}
				
			return $this;
		}
		
		protected function _getWhereSql() {
			$res = '';
			
			if($data = $this->_getSqlData('where')) {
				$sqlOrs = array();
				
				foreach($data as $whereOrs) {
					$sqlAnds = array();
				
					foreach($whereOrs as $whereAndSql) {
						$sqlAnds[] = '('.$whereAndSql.')';
					}
					
					$sql = implode(') AND (', $sqlAnds);
					
					$sqlOrs[] = $sql;
				}
				
				$sql = implode(') OR ('." \n", $sqlOrs);
				
				$res = 'WHERE ('.$sql.')';
			}
			
			return $res;
		}
		
		public function orWhere($where, array $params = array()) {
			$this->_sqlPartsData['where'][][] = $this->_sqlUtil->getWhereString($where, false, $params);
				
			return $this;
		}
		
		public function groupBy($column) {
			$this->_registerSqlData('groupBy', $column);
			
			return $this;
		}
		
		protected function _getGroupBySql() {
			if($column = $this->_getSqlData('column')) {
			
				$sql = 'GROUP BY '.$column;
				
				return $sql;
			}
			else {
				return '';
			}
		}
		
		public function having($havingClause) {
			$this->_registerSqlData('having', $havingClause);
			
			return $this;
		}
		
		protected function _getHavingSql() {
			if($havingClause = $this->_getSqlData('having')) {
				$sql = 'HAVING '.$havingClause;

				return $sql;
			}
			else {
				return '';
			}
		}
		
		public function orderBy($orderBy) {
			$this->_registerSqlData('orderBy', $this->_sqlUtil->getOrderByStr($orderBy));
			
			return $this;
		}
		
		protected function _getOrderBySql() {
			if($sql = $this->_getSqlData('orderBy')) {
				return $sql;
			}
			else {
				return '';
			}
		}
		
		public function limit($limit, $offset = 0) {
			$this->_registerSqlData('limit', 
				call_user_func_array(array($this->_sqlUtil, 'getLimitStr'), func_get_args())
			);
			
			return $this;
		}
		
		protected function _getLimitSql() {
			if($sql = $this->_getSqlData('limit')) {

				return $sql;
			}
			else {
				return '';
			}
		}
		
		/**
		* Return the resulting sql
		*/
		public function sql() {
			$parts[] = $this->_getFromSql();
			$parts[] = $this->_getJoinSql();
			$parts[] = $this->_getWhereSql();
			$parts[] = $this->_getGroupBySql();
			$parts[] = $this->_getHavingSql();
			$parts[] = $this->_getOrderBySql();
			$parts[] = $this->_getLimitSql();
			
			$realParts = array();
			
			foreach($parts as $part) {
				if($part) {
					$realParts[] = $part;
				}
			}
			
			$sql = implode(" \n", $realParts);
			
			return $sql;
		}
		
		/**
		* Return the result of the sql query ($QueryExecuterInterface must be set in the contructor)
		*/
		public function getResults($fetchArgs = null) {
			if($this->_queryExecuter) {
				if(is_null($fetchArgs)) {
					return call_user_func(array($this->_queryExecuter, 'getResults'), $this->sql(), array(\PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE, $this->_rowsClass));
				}
				else {
					return call_user_func(array($this->_queryExecuter, 'getResults'), $this->sql(), $fetchArgs);
				}
			}
			else {
				throw new \Aspic\Exception('No query executer has been set to be used in exec method');
			}
		}
		
		public function __toString() {
			return $this->sql();
		}
		
		/**
		* Shortcut to the Util object
		*/
		public function util() {
			return $this->_sqlUtil;
		}
		
		protected function _registerSqlData($name,$data) {
			$this->_sqlPartsData[$name] = $data;
		}
		
		protected function _appendSqlData($name,$data) {
			$this->_sqlPartsData[$name][] = $data;
		}
		
		protected function _getSqlData($name) {
			if(isset($this->_sqlPartsData[$name])) {
				return $this->_sqlPartsData[$name];
			}
			else {
				return null;
			}
		}
		
		/**
		* Return the table name (index 0) and it's alias (index 1, if it exists) from a table string
		*/
		protected function _parseTable($table) {
			$arrTable = explode(' ', $table);
			
			$ret[0] = $arrTable[0];
			$ret[1] = isset($arrTable[1])?$arrTable[1]:'';
			
			return $ret;
		}
		
		protected function _getTableSqlString($table) {
			list($tableName, $tableAlias) = $this->_parseTable($table);
			
			$tableStr = $tableName.(($tableAlias)?' '.$tableAlias:'');
			
			return $tableStr;
		}
		
	}	
	
?>